/*
 * Copyright (c) 2020 - present, Inspur Genersoft Co., Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *        http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.inspur.edp.web.approvalformat.core.util;

import com.inspur.edp.cef.designtime.api.IGspCommonField;
import com.inspur.edp.cef.designtime.api.collection.GspAssociationCollection;
import com.inspur.edp.cef.designtime.api.collection.GspEnumValueCollection;
import com.inspur.edp.cef.designtime.api.collection.GspFieldCollection;
import com.inspur.edp.cef.designtime.api.element.GspAssociation;
import com.inspur.edp.cef.designtime.api.element.GspElementObjectType;
import com.inspur.edp.formserver.viewmodel.GspViewModelElement;
import com.inspur.edp.lcm.metadata.api.entity.GspMetadata;
import com.inspur.edp.metadata.rtcustomization.api.CustomizationService;
import com.inspur.edp.udt.designtime.api.entity.ComplexDataTypeDef;
import com.inspur.edp.udt.designtime.api.entity.SimpleDataTypeDef;
import com.inspur.edp.udt.designtime.api.entity.UnifiedDataTypeDef;
import com.inspur.edp.udt.designtime.api.entity.element.ElementCollection;
import com.inspur.edp.udt.designtime.api.entity.element.UdtElement;
import com.inspur.edp.web.approvalformat.api.entity.schema.ComplexField;
import com.inspur.edp.web.approvalformat.api.entity.schema.Field;
import com.inspur.edp.web.approvalformat.api.entity.schema.SimpleField;
import com.inspur.edp.web.approvalformat.api.entity.schema.editor.*;
import com.inspur.edp.web.approvalformat.api.entity.schema.type.*;
import com.inspur.edp.web.common.utility.StringUtility;
import io.iec.edp.caf.commons.utils.SpringBeanUtils;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

/**
 * @author Xu‘fa Wang
 * @date 2020/7/7 17:09
 */
public class FieldUtil {
    private static final CustomizationService customizationService = SpringBeanUtils.getBean(CustomizationService.class);

    public static Field createField(IGspCommonField element, TypeBuildingContext parentContext) {
        Field createdField;
        // 按照从子类到父类的方式检查使用的类型
        if(element instanceof GspViewModelElement) {
            createdField = constructField((GspViewModelElement) element, parentContext);
        } else if(element instanceof UdtElement) {
            createdField = constructField((UdtElement) element, parentContext);
        } else {
            throw new RuntimeException("不支持类型，请联系开发人员支持。当前字段是： "
                    +  element.getCode() + " : " + element.getName());
        }

        return createdField;
    }

    private static Field constructField(SimpleDataTypeDef udtTypeDef, TypeBuildingContext parentContext) {
        TypeBuildingContext elementContext = TypeBuildingContext.create(udtTypeDef, parentContext);
        return constructField(elementContext, parentContext);
    }

    private static Field constructField(GspViewModelElement element, TypeBuildingContext parentContext) {
        TypeBuildingContext elementContext = TypeBuildingContext.create(element, parentContext);
        return constructField(elementContext, parentContext);
    }

    private static Field constructField(UdtElement element, TypeBuildingContext parentContext) {
        TypeBuildingContext elementContext = TypeBuildingContext.create(element, parentContext);
        return constructField(elementContext, parentContext);
    }

    public static Field constructField(TypeBuildingContext elementContext, TypeBuildingContext parentContext){
        String prefix = constructBindingFieldPrefix(parentContext);
        Map<String, Object> elementParams = elementContext.getParams();
        String id = (String) elementParams.get("id");
        String code = (String) elementParams.get("code");
        String name = (String) elementParams.get("name");
        String label = (String) elementParams.get("label");
        Boolean multiLanguageInput = (Boolean) elementParams.get("multiLanguageInput");

        elementParams.put("bindingField", prefix + label);
        String bindingField = (String) elementParams.get("bindingField");

        elementParams.put("id", elementContext.reviseElementId(id));
        String revisedElementId = (String) elementParams.get("id");

        elementParams.put("path", constructFieldPath(elementContext, parentContext));
        String path = (String) elementParams.get("path");

        elementParams.put("bindingPath", constructBindingPath(elementContext, parentContext));
        String bindingPath =(String) elementParams.get("bindingPath");

        if(elementContext.hasAssociation() || elementContext.hasUnifiedDataType() || elementContext.isDynamicField()){
            return new ComplexField(){
                {
                    this.setId(revisedElementId);
                    this.setOriginalId(id);
                    this.setCode(code);
                    this.setName(name);
                    this.setLabel(StringUtility.toCamelCase(label));
                    this.setBindingField(StringUtility.toCamelCase(bindingField));
                    this.setPath(path);
                    this.setBindingPath(bindingPath);
                    this.setType(constructFieldType(elementContext, parentContext));
                }
            };
        }
        Boolean require = (Boolean) elementContext.getParams().get("require");
        String defaultValue = (String) elementContext.getParams().get("defaultValue");
        Boolean isReadOnly = (Boolean) elementContext.getParams().get("readonly");
        return new SimpleField(){
            {
                this.setId(revisedElementId);
                this.setOriginalId(id);
                this.setCode(code);
                this.setName(name);
                this.setLabel(StringUtility.toCamelCase(label));
                this.setBindingField(StringUtility.toCamelCase(bindingField));
                this.setPath(path);
                this.setBindingPath(bindingPath);
                this.setReadonly(isReadOnly);
                this.setRequire(require);
                this.setDefaultValue(defaultValue);
                this.setMultiLanguage(multiLanguageInput);
                this.setType(constructFieldType(elementContext, parentContext));
                this.setEditor(constructFieldEditor(elementContext));
            }
        };
    }

    private static String constructBindingFieldPrefix(TypeBuildingContext context){
        String prefix = "";
        if (context != null){
            prefix = context.getParams().get("bindingField") + "_";
        }
        return prefix;
    }

    private static String constructFieldPath(TypeBuildingContext elementContext, TypeBuildingContext parentTypeBuildingContext) {
        String label = (String) elementContext.getParams().get("label");
        String prefix = "";
        if (parentTypeBuildingContext != null) {
            prefix = (String) parentTypeBuildingContext.getParams().get("path");
        }
        if (!prefix.isEmpty()) {
            return prefix+"."+label;
        }
        return label;
    }

    private static String constructBindingPath(TypeBuildingContext elementContext, TypeBuildingContext parentTypeBuildingContext){
        String label = StringUtility.toCamelCase((String) elementContext.getParams().get("label"));
        String prefix = "";
        if (parentTypeBuildingContext != null) {
            prefix = (String) parentTypeBuildingContext.getParams().get("bindingPath");
        }
        if (!prefix.isEmpty()) {
            return prefix+"."+label;
        }
        return label;
    }

    private static FieldType constructFieldType(TypeBuildingContext elementContext, TypeBuildingContext parentContext) {
        if(elementContext.hasUnifiedDataType()) {
            return constructObjectFieldType(elementContext);
        }

        if(elementContext.hasAssociation()) {
            return constructEntityFieldType(elementContext,parentContext);
        }

        if(elementContext.getObjectType() == GspElementObjectType.Enum) {
            return constructEnumFieldType(elementContext);
        }

        if(elementContext.isDynamicField()) {
            return constructDynamicFieldType(elementContext);
        }

        return constructSimpleFieldType(elementContext);
    }

    private static FieldType constructObjectFieldType(TypeBuildingContext elementContext) {
        UnifiedDataTypeDef udtTypeDef = constructUnifiedDataType(elementContext.getUnifiedDataType());
        List<Field> fields = new ArrayList<>();
        String typeName = "", displayTypeName = "";
        if(udtTypeDef instanceof SimpleDataTypeDef) {
            Field field = constructField((SimpleDataTypeDef) udtTypeDef, elementContext);
            if(field instanceof SimpleField){
                ((SimpleField) field).setRequire((Boolean) elementContext.getParams().get("require"));
                ((SimpleField) field).setReadonly((Boolean) elementContext.getParams().get("readonly"));
            }
            typeName = elementContext.reviseTypeName(field.getId(), field.getCode());
            displayTypeName = udtTypeDef.getName();
            fields.add(field);
        }
        if(udtTypeDef instanceof ComplexDataTypeDef) {
            typeName = elementContext.reviseTypeName(udtTypeDef.getID(),udtTypeDef.getCode());
            displayTypeName = udtTypeDef.getName();

            ElementCollection elementCollection = ((ComplexDataTypeDef) udtTypeDef).getElements();
            if(elementCollection != null && elementCollection.size() > 0) {
                elementCollection.forEach(field -> {
                    Field createdField = createField(field, elementContext);
                    fields.add(createdField);
                });
            }
        }
        if(!(udtTypeDef instanceof SimpleDataTypeDef)&&!(udtTypeDef instanceof ComplexDataTypeDef)) {
            throw new RuntimeException("Id为"+udtTypeDef.getId()+"的统一数据类型元数据为未识别的类型。");
        }

        ObjectType objectType = new ObjectType(typeName, displayTypeName, fields);
        if("dbfbe55d-ba65-4a7f-a9d4-4f664ec6ec68".equals(udtTypeDef.getId())
                || "12be876c-368c-4262-88ab-4112688540b0".equals(udtTypeDef.getId())) {
            objectType = new HierarchyType(typeName, displayTypeName, fields);
        }

        return objectType;
    }

    private static FieldType constructEntityFieldType(TypeBuildingContext elementContext, TypeBuildingContext parentContext) {
        GspAssociationCollection associations = elementContext.getAssociations();
        if (associations == null || associations.size() == 0) {
            throw new RuntimeException("字段" + elementContext.getParams().get("name") + "不包含关联实体信息。");
        }
        TypeBuildingContext originalFieldContext = TypeBuildingContext.createSimpleTypeContextFromAssociation(elementContext, parentContext);
        Field originalField = constructField(originalFieldContext, parentContext);
        List<Field> typeFields = new ArrayList<>();
        typeFields.add(originalField);
        GspAssociationCollection associationCollection = elementContext.getAssociations();
        if(associationCollection != null && associationCollection.size() > 0) {
            associationCollection.forEach(association -> {
                GspFieldCollection refElementCollection = association.getRefElementCollection();
                List<Field> fieldCollection = new LinkedList<>();
                if(refElementCollection != null && refElementCollection.size() > 0) {
                    refElementCollection.forEach(refEle -> {
                        Field createdField = createField(refEle, elementContext);
                        fieldCollection.add(createdField);
                    });
                }
                if(fieldCollection.size() > 0) {
                    typeFields.addAll(fieldCollection);
                }
            });
        }

        GspAssociation anyAss = elementContext.getAssociations().get(0);
        String typeName = elementContext.reviseTypeName(originalField.getId(), anyAss.getRefModelCode());
        String displayTypeName = anyAss.getRefModelName();
        return new EntityType(typeName, displayTypeName, originalField.getLabel(), typeFields, new ArrayList<>());
    }

    private static UnifiedDataTypeDef constructUnifiedDataType(String uri) {
        GspMetadata udtMetadata = customizationService.getMetadata(uri);
        if(udtMetadata == null) {
            throw new RuntimeException("未获取到id为" + uri + "的统一数据类型(UDT)元数据。");
        }

        UnifiedDataTypeDef typeDef = (UnifiedDataTypeDef) udtMetadata.getContent();
        return typeDef;
    }

    private static FieldType constructEnumFieldType(TypeBuildingContext elementContext) {
        FieldType valueType = constructSimpleFieldType(elementContext);

        List<EnumItem> enumValues = new ArrayList<>();
        GspEnumValueCollection enumValueCollection = elementContext.getEnums();
        if(enumValueCollection != null && enumValueCollection.size() > 0) {
            enumValueCollection.forEach(item -> enumValues.add(new EnumItem(item.getValue(), item.getName())));
        }

        return new EnumType(valueType, enumValues);
    }

    private static FieldType constructDynamicFieldType(TypeBuildingContext elementContext) {
        return new DynamicObjectType();
    }

    private static FieldType constructSimpleFieldType(TypeBuildingContext elementContext) {
        switch (elementContext.getDataType()) {
            case Boolean:
                return new BooleanType();
            case Date:
            case DateTime:
                return new DateType();
            case Decimal:
            case Integer:
                return new NumericType((Integer)elementContext.getParams().get("length"), (Integer)elementContext.getParams().get("precision"));
            case Text:
                return new TextType((Integer)elementContext.getParams().get("length"));
            default:
                return new StringType((Integer)elementContext.getParams().get("length"));
        }
    }

    private static FieldEditor constructFieldEditor(TypeBuildingContext elementContext) {
        if(elementContext.getObjectType() == GspElementObjectType.Enum) {
            return new EnumField();
        }

        if(elementContext.isMultiLanguageField()) {
            return new LanguageTextBox();
        }

        if(elementContext.getObjectType() == GspElementObjectType.None) {
            switch (elementContext.getDataType()){
                case Boolean:
                    return new CheckBox();
                case Date:
                case DateTime:
                    return new DateBox("yyyy-MM-dd");
                case Decimal:
                case Integer:
                    return new NumericBox();
                case Text:
                    return new MultiTextBox();
                default:
                    return new TextBox();
            }
        }

        return new TextBox();
    }
}
